#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <boost/filesystem.hpp>
#include "Log.hpp"
#include "util.hpp"

extern Log lg;

const std::string src_root_path = "./data/input";
const std::string raw_path = "./data/raw_html/raw.bin";
const std::string official_url_header = "https://www.boost.org/doc/libs/1_84_0/doc/html/";

enum
{
    ReadFileNameError = 1,
    StorageFileError,
    OutputError
};

struct FileData
{
    std::string title;
    std::string content;
    std::string url;
};

bool ReadFileName(std::vector<std::string> *filename_list, const std::string &path);
bool StorageFileData(std::vector<FileData> *filedata_list, const std::vector<std::string> &filename_list);
bool OutputFileData(const std::string &path, const std::vector<FileData> &filedata_list);

int main()
{
    // 1.从src_root_path中，通过递归读取的方式，把所有的 ".html" 文件，附带路径的将文件名存储下来
    std::vector<std::string> filename_list;
    if (!ReadFileName(&filename_list, src_root_path)) // 输出形参数，输入形参数
    {
        // 读取文件名失败，程序退出
        lg(Fatal, "read filename fail\n");
        exit(ReadFileNameError);
    }

    // 2.通过读取到的filename，找到文件并打开，将其中的内容去标签化，并且存储下来
    std::vector<FileData> filedata_list;
    if (!StorageFileData(&filedata_list, filename_list)) // 输出形参数，输入形参数
    {
        // 存储文件内容失败，程序退出
        lg(Fatal, "storage file fail\n");
        exit(StorageFileError);
    }

    // 3.将去标签，格式化存储下来的数据，以\3为分隔符将其存储到raw_path所指向的文件当中
    if (!OutputFileData(raw_path, filedata_list)) // 输入形参数，输入形参数
    {
        lg(Fatal, "output fail\n");
        exit(OutputError);
    }

    lg(Info, "parser success\n");

    return 0;
}

bool ReadFileName(std::vector<std::string> *filename_list, const std::string &path)
{
    namespace fs = boost::filesystem;
    fs::path rootpath(path);

    if (!fs::exists(rootpath))
    {
        return false;
    }

    fs::recursive_directory_iterator end;
    // fs::recursive_directory_iterator it(rootpath);

    for (fs::recursive_directory_iterator it(rootpath); it != end; it++)
    {
        // 1.必须是普通文件
        if (!fs::is_regular_file(*it))
        {
            continue;
        }
        // 2.文件必须是以".html"为结尾
        if (it->path().extension() != ".html")
        {
            continue;
        }
        // lg(Info, "get filename: %s\n", it->path().string().c_str());
        filename_list->push_back(it->path().string());
    }

    return true;
}

bool ParseTitle(const std::string &inbuffer_stream, std::string *title)
{
    size_t begin = inbuffer_stream.find("<title>");
    if (begin == std::string::npos)
    {
        return false;
    }
    begin = begin + std::string("<title>").size();

    size_t end = inbuffer_stream.find("</title>");
    if (end == std::string::npos)
    {
        return false;
    }

    if (begin > end)
    {
        return false;
    }

    *title = inbuffer_stream.substr(begin, end - begin);
    return true;
}

bool ParseContent(const std::string &inbuffer_stream, std::string *content)
{
    // 一个简易的状态机
    enum state
    {
        LABEL = 1,
        CONTENT = 2
    };
    // 所有的html文件一开始都处于LABLE状态
    enum state cur = LABEL;
    for (char ch : inbuffer_stream)
    {
        if (cur == LABEL)
        {
            if (ch == '>')
            {
                // 此时说明标签结束
                cur = CONTENT;
            }
        }
        else if (cur == CONTENT)
        {
            if (ch == '<')
            {
                cur = LABEL;
                continue;
            }
            if (ch == '\n')
            {
                *content += ' ';
            }
            *content += ch;
        }
        else
        {
            // 意料之外的情况
            return false;
        }
    }
    return true;
}

bool ParseUrl(const std::string &filename, const std::string &official_url_header, std::string *url)
{
    // filename:./data/input/accumulators.html
    // 官方url:https://www.boost.org/doc/libs/1_84_0/doc/html/accumulators.html
    // official_url_header:https://www.boost.org/doc/libs/1_84_0/doc/html/
    
    size_t pos = filename.rfind('/');
    if (pos == std::string::npos)
    {
        return false;
    }
    *url = official_url_header; 
    *url += filename.substr(pos + 1);

    return true;
}

void debug_print_fileData(FileData& fd)
{
    std::cout << "title: " << fd.title << std::endl;
    std::cout << "content: " << fd.content << std::endl;
    std::cout << "url: " << fd.url << std::endl;
}

bool StorageFileData(std::vector<FileData> *filedata_list, const std::vector<std::string> &filename_list)
{
    for (std::string filename : filename_list)
    {
        std::string inbuffer_stream;
        if (!ns_util::ReadFile(filename, &inbuffer_stream)) // 输入形参数,输出形参数
        {
            // lg(Warning, "read file %s fail\n", filename.c_str());
            continue;
        }
        // 解析title
        FileData fd;
        if (!ParseTitle(inbuffer_stream, &(fd.title))) // 输入形参数,输出形参数
        {
            // lg(Warning, "parse title fail\n");
            continue;
        }
        // 解析content
        if (!ParseContent(inbuffer_stream, &(fd.content))) // 输入形参数,输出形参数
        {
            // lg(Warning, "parse content fail\n");
            continue;
        }
        // 解析url
        if (!ParseUrl(filename, official_url_header, &(fd.url))) // 输入形参数,输入形参数,输出形参数
        {
            // lg(Warning, "parse url fail\n");
            continue;
        }

        // debug 打印一次,看看效果
        // debug_print_fileData(fd);
        // break;
        filedata_list->push_back(std::move(fd));
    }

    return true;
}

bool OutputFileData(const std::string &path, const std::vector<FileData> &filedata_list)
{
    // 一个文本内的分隔符
    const std::string SEP_IN = "\3";
    // 多个文本间的分隔符
    const std::string SEP_OUT = "\n";

    std::ofstream out(path, std::ios::out | std::ios::binary);
    if (!out.is_open())
    {
        return false;
    }

    for (const FileData& fd : filedata_list)
    {
        std::string outstream_buffer = fd.title;
        outstream_buffer += SEP_IN;
        outstream_buffer += fd.content;
        outstream_buffer += SEP_IN;
        outstream_buffer += fd.url;
        outstream_buffer += SEP_OUT;

        out.write(outstream_buffer.c_str(), outstream_buffer.size());
    }

    out.close();
    return true;
}
